Skip to content

fix(mobile): guard against OOM crash on Load, fix stuck Preparing audio - #235

Merged
thcp merged 11 commits into
mainfrom
fix/mobile-load-oom-234
Jun 29, 2026
Merged

fix(mobile): guard against OOM crash on Load, fix stuck Preparing audio#235
thcp merged 11 commits into
mainfrom
fix/mobile-load-oom-234

Conversation

@thcp

@thcp thcp commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Fixes #234

Changes

Bug 1 - OOM crash on Load (openTrack):

Mobile browsers (Safari/Chrome on iOS/Android) kill tabs that exceed ~200-500 MB. createAudioEngine decodes all stems into AudioBuffer objects in parallel - for a 5-min 4-stem track that peaks at ~420 MB. Added a size check using estimateDecodedBytes before the engine is created. Tracks over 200 MB (~4.5 min x 4 stems) now show a clear error instead of crashing the tab.

Bug 2 - Stuck "Preparing audio" after reload (loadLibrary):

After a crash-induced reload, loadLibrary was auto-selecting state.tracks[0] without calling openTrack(). This set state.current (with loading: false, error: null) while engineReady stayed false, making preparing permanently true. Removed the auto-select: the library renders on load and the user picks a track explicitly.

Test

  1. Load a short track (<4.5 min) on mobile - plays normally
  2. Load a long track (>5 min) on mobile - shows "Track too long to load on mobile" error instead of crashing
  3. Reload the page - library loads, no track auto-selected, no "Preparing audio" shown

thcp added 8 commits June 29, 2026 12:04
Two bugs reported in #234 (via discussion #216):

1. Mobile browsers crash (WebKit tab kill) when loading long tracks because
   createAudioEngine decodes all stems into AudioBuffers in parallel. Added
   an estimateDecodedBytes check before creating the engine: tracks whose
   decoded PCM would exceed 200 MB (approx 4.5 min x 4 stems) now surface a
   clear error instead of silently crashing the tab.

2. After a crash-induced reload, loadLibrary auto-selected state.tracks[0]
   without calling openTrack(), leaving the player stuck on "Preparing audio"
   indefinitely. Removed the auto-select: the library is visible on load and
   the user can pick a track explicitly.

Closes #234
Instead of blocking long tracks with an error, tracks that would exceed
200 MB of decoded PCM (approx 4.5 min x 4 stems) now fall back to a
streaming engine backed by <audio> elements and createMediaElementSource.
No PCM is held in RAM -- the browser streams on demand -- so OOM crashes
are avoided without restricting track length.

The streaming engine implements the same interface as createAudioEngine
(play/pause/seek/setGain/destroy/ready) so openTrack needs no structural
changes beyond selecting which engine to create.
Play button: add data-playing attribute and CSS so the button turns green
when playing (gold when paused), making the state immediately obvious.

Seek desync: the streaming engine was setting currentTime on all <audio>
elements while they continued playing, causing each stem to arrive at the
new position at a slightly different time. Fix: pause all elements first,
seek all, then resume -- this guarantees all stems restart from the same
position simultaneously.
… style

Prev/next buttons were rendered but had no data-action and no handler --
clicking them did nothing. Now:

- prevTrack() / nextTrack() navigate state.tracks by index, autoplaying if
  a track was already playing when the button was pressed
- Buttons are disabled at the ends of the library (no prev on first track,
  no next on last track)
- .t-step CSS updated to match the desktop daw-iconbtn style: transparent
  background, rounded corners, hover highlight, scale(0.96) on active
Two-part fix for stems going out of sync after dragging the playhead:

1. Seek: pause all elements, set all currentTime, then wait one RAF frame
   for the browser to settle the seeks. Re-read the primary element's
   actual position and align all secondaries to it before calling play().
   This eliminates startup desync caused by elements buffering at different
   rates after a seek.

2. Drift correction: tick() now checks secondary elements every ~1 second
   (60 RAF frames) and snaps any that have drifted more than 50ms back to
   the primary's position. Catches any clock skew that accumulates during
   long playback.
…er style

Remove the rectangular box shape (border-radius:9px) that clashed with the
circular play button. Prev/next are now naked icon buttons with a circular
tap area -- the pattern used by Spotify, Apple Music, etc. Icons enlarged
from 26px to 32px to sit proportionally next to the 66px play button.
Press feedback is a scale+opacity pop instead of a subtle background fill.
The streaming engine (<audio> elements via createMediaElementSource) caused
constant choppy audio -- the exact same HTTP/1.1 connection-cap underrun
issue that audioEngine.js was built to solve in the first place.

Replace with the buffer engine for all tracks. Raise the decoded-PCM limit
from 200 MB to 600 MB, which covers ~14 min x 4 stems at 44.1 kHz/Float32
-- well within the per-tab budget of any post-2019 phone. Tracks over that
threshold get a clear error rather than glitchy playback.
thcp added 3 commits June 29, 2026 15:09
600 MB / (stems * 2ch * 44100Hz * 4 bytes) = ~7 min for 4 stems, not 14.
The 14-minute figure was only accurate for a 2-stem track. Error message
now computes and shows the real cap based on the actual stem count.
@thcp
thcp marked this pull request as ready for review June 29, 2026 14:55
@thcp
thcp merged commit 0e25f15 into main Jun 29, 2026
8 checks passed
@thcp
thcp deleted the fix/mobile-load-oom-234 branch June 29, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile: WebKit OOM crash on Load + stuck Preparing audio after reload

1 participant